package dev.ece.suit.loader.models.ms3d;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import dev.ece.core.cells.model.action.ActionManager.Action;
import dev.ece.suit.loader.Face;
import dev.ece.suit.loader.Joint;
import dev.ece.suit.loader.Material;
import dev.ece.suit.loader.Mesh;
import dev.ece.suit.loader.ModelDepthShader;
import dev.ece.suit.loader.ModelLoader;
import dev.ece.suit.loader.ModelShader;
import dev.ece.util.io.LittleEndianInputStream;
import dev.ece.util.lib.Vector3f;

public class MS3DModelLoader implements ModelLoader<Float> {

	private static final Logger logger = LogManager.getLogger(MS3DModelLoader.class);
	
	private LittleEndianInputStream input;
	
	private MS3DHeader header;
	
	private MS3DVertex[] vertice;
	
	private MS3DTriangle[] triangles;
	
	private MS3DGroup[] groups;
	
	private MS3DMaterial[] materials;
	
	private MS3DJoint[] joints;
	
	private Vector3f[] boundBox;
	
	private float fps;
	
	private float currentTime;
	
	private float totalTime;
	
	private int frameCount;
	
	private String comment;
	
	public MS3DModelLoader(File file) throws IOException {
		this(new FileInputStream(file));
	}
	
	public MS3DModelLoader(InputStream input) throws IOException {
		this.input = new LittleEndianInputStream(input);
	}
	
	@Override
	public void load() throws IOException {
		this.header = MS3DHeader.load(input);
		logger.debug(String.format("header id:%s", this.header.getId()));
		logger.debug(String.format("header version:%s", this.header.getVersion()));
		this.vertice = MS3DVertex.load(input);
		logger.debug(String.format("vertice:%s", this.vertice.length));
		this.boundBox = MS3DVertex.boundBox(vertice);
		this.triangles = MS3DTriangle.load(input);
		logger.debug(String.format("triangles:%s", this.triangles.length));
		this.groups = MS3DGroup.load(input);
		logger.debug(String.format("groups:%s", this.groups.length));
		this.materials = MS3DMaterial.load(input);
		logger.debug(String.format("materials:%s", this.materials.length));
		this.fps = input.readFloat();
		logger.debug(String.format("fps:%s", this.fps));
		this.currentTime = input.readFloat();
		this.frameCount = input.readInt();
		this.joints = MS3DJoint.load(input);
		logger.debug(String.format("joints:%s", this.joints.length));
		this.totalTime = this.frameCount / this.fps;
		logger.debug(String.format("totalTime:%s", this.totalTime));
		this.loadComment(input);
		this.loadExtra(input);
	}

	private void loadComment(LittleEndianInputStream is) throws IOException {
		if(is.available() > 0) {
			int subVersion = is.readInt();
			if(subVersion == 1) {
				MS3DGroup.loadComment(is, groups);
				MS3DMaterial.loadComment(is, materials);
				MS3DJoint.loadComment(is, joints);
				int count = is.readInt();
				if(count == 1) {
					long commentSize = is.readUnsignedInt();
					if(commentSize > 0) {
						comment = is.readString((int) commentSize);
					}
				}
			}
		}
	}
	
	private void loadExtra(LittleEndianInputStream is) throws IOException {
		MS3DVertex.loadExtra(is, vertice);
	}
	
	@Override
	public Float total() {
		return this.totalTime;
	}

	@Override
	public int frameCount() {
		return this.frameCount;
	}

	@Override
	public Material[] getMaterials() {
		return this.materials;
	}

	@Override
	public Mesh[] getMeshes() {
		MS3DMesh[] meshes = new MS3DMesh[this.groups.length];
		for(int i=0; i<meshes.length; i++) {
			meshes[i] = new MS3DMesh(this.groups[i].getIndice(), this.groups[i].getMaterialIndex());
			meshes[i].fillData(vertice, triangles);
		}
		return meshes;
	}

	@Override
	public Face[] getFaces() {
		return null;
	}

	@Override
	public Joint<Float>[] getUpdateJoints() {
		return this.joints;
	}

	@Override
	public ModelShader getModelShader() {
		return ModelShader.getDefaultModelShader();
	}

	@Override
	public ModelDepthShader getModelDepthShader() {
		return ModelDepthShader.getDefaultModelDepthShader();
	}

	@Override
	public void addAction(Map<String, Action<Float>> actions) {
		actions.put("all", new Action<Float>("all", 0f, this.totalTime, true));
	}

	@Override
	public Vector3f[] boundBox() {
		return this.boundBox;
	}

	public LittleEndianInputStream getInput() {
		return input;
	}

	public MS3DHeader getHeader() {
		return header;
	}

	public MS3DVertex[] getVertice() {
		return vertice;
	}

	public MS3DTriangle[] getTriangles() {
		return triangles;
	}

	public MS3DGroup[] getGroups() {
		return groups;
	}

	public float getFps() {
		return fps;
	}

	public float getCurrentTime() {
		return currentTime;
	}

	public float getTotalTime() {
		return totalTime;
	}

	public int getFrameCount() {
		return frameCount;
	}

	public String getComment() {
		return comment;
	}

	@Override
	public void dispose() {
		this.input.close();
		vertice = null;
		triangles = null;
		groups = null;
		materials = null;
		joints = null;
		input = null;
	}

}
